using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;

namespace ZerAx.ObjectNotation
{
    public abstract class Zon
    {
        public abstract ZonType Type { get; }

        public abstract void SerializeData(BinaryWriter writer);
        public void Write(Stream stream) => Write(stream, Encoding.UTF8);
        public void Write(Stream stream, Encoding encoding)
        {
            Serialize(new BinaryWriter(stream, encoding));
        }
        public void Serialize(BinaryWriter writer)
        {
            writer.Write(Type.ToByte());
            SerializeData(writer);
        }

        public abstract void Initialize(BinaryReader reader);
        public static Zon Read(Stream stream) => Read(stream, Encoding.UTF8);
        public static Zon Read(Stream stream, Encoding encoding)
        {
            return Deserialze(new BinaryReader(stream, encoding));
        }
        public static Zon Deserialze(BinaryReader reader)
        {
            var type = reader.ReadByte().ToZonType();
            var obj = ZonObjFactory(type);
            obj.Initialize(reader);
            return obj;
        }

        private static Zon ZonObjFactory(ZonType type)
        {
            return type switch
            {
                ZonType.Bool => new ZonBool(),
                ZonType.U8 => new ZonU8(),
                ZonType.I32 => new ZonI32(),
                ZonType.F32 => new ZonF32(),
                ZonType.Vec3I => new ZonVec3I(),
                ZonType.Vec3 => new ZonVec3(),
                ZonType.String => new ZonString(),
                ZonType.I32Array => new ZonI32Array(),
                ZonType.List => new ZonList(),
                ZonType.Tagged => new ZonTagged(),
                ZonType.Compound => new ZonCompound(),
                _ => null,
            };
        }

        public byte[] ToByteArray()
        {
            using var output = new MemoryStream();
            Serialize(new BinaryWriter(output));
            return output.ToArray();
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public T As<T>() where T : Zon => this as T;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool? AsBool() => As<ZonBool>()?.data;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public byte? AsU8() => As<ZonU8>()?.data;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int? AsI32() => As<ZonI32>()?.data;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public float? AsF32() => As<ZonF32>()?.data;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public string AsString() => As<ZonString>()?.data;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public (float x, float y, float z)? AsVec3() => As<ZonVec3>()?.data;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public (int x, int y, int z)? AsVec3i() => As<ZonVec3I>()?.data;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ZonCompound AsCompound() => As<ZonCompound>();
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public T AsOrDefault<T>() where T : Zon => this as T ?? (T)ZonObjFactory(Type);
        

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static implicit operator Zon (string val) => new ZonString(val);
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static implicit operator Zon (bool val) => new ZonBool(val);
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static implicit operator Zon (byte val) => new ZonU8(val);
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static implicit operator Zon (int val) => new ZonI32(val);
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static implicit operator Zon (float val) => new ZonF32(val);
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static implicit operator Zon ((string tag, Zon val) t) => new ZonTagged(t.tag, t.val);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Zon Vec3i(int x, int y, int z) => new ZonVec3I((x, y, z));
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Zon Vec3(float x, float y, float z) => new ZonVec3((x, y, z));
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Zon Tag(string tag, Zon data) => new ZonTagged(tag, data);
        public static Zon I32Array(params int[] data) => new ZonI32Array(data);
        public static Zon List(params Zon[] data) => new ZonList(data);
        public static Zon List(IEnumerable<Zon> data) => new ZonList(data);
        public static Zon Compound(params (string, Zon)[] data) => new ZonCompound(data);
        public static Zon Compound(IEnumerable<KeyValuePair<string, Zon>> data) => new ZonCompound(data);
    }
}
